#include <windows.h>
#include <tchar.h>
#include <vector>
#include <commctrl.h>
#include <shellAPI.h>

#include <string.h>

#include "lib.h"
#include "patchui.h"
#include "update.h"
#include "resids.h"
#include "pkgdesc.h"
#include "lview.h"
#include "casbase.h"
#include "pkgmgr.h"
#include "pathname.h"

#include "dir.h"
#include "stream.h"

#include "zipfile.h"

#define SELF	PatchUI
#define SUPER	SyncDialog

#define WM_UPDATEHEADERWIDTH	(WM_USER + 1010)

#define PATCHPIPE	L"\\\\.\\pipe\\rtg%spatch"

// initial connection starts in response to windows msgs to allow UI to repaint
//
#define PUI_CONNECT	(WM_USER + 1011)
#define PUI_LOGIN	(WM_USER + 1012)

std::vector<TCHAR> ConvertStringToTCharVector( const String& s )
{	
	std::vector<TCHAR> temp( s.getLength() + 1 );

	Library::StringConversions::convert( &temp[0], s, s.getLength() );

	return temp;
}

std::vector<char> ConvertStringToCharVector( const String& s )
{	
	std::vector<char> temp( s.getLength() + 1 );

	Library::StringConversions::convert( &temp[0], s, s.getLength() );

	return temp;
}

enum AdminPipeErrors	/* APE: must stay in sync with patch/admin/admpipe.h */
{
	APE_UNKNOWN_REQUEST,
	APE_MEMORY_ERROR,
	APE_ATTEMPT_RELOGIN,
	APE_LIST_NO_MORE,
	APE_UNKNOWN_PACKAGE,
	APE_INVALID_UPDATE,
	APE_INVALID_ADD,
	APE_INVALID_REMOVE,

	// the rest are internal to this app
	APE_NONE = -1,
	APE_UI_INTERNAL = -2
};

enum States
{
	PUIS_NOTCONNECTED, PUIS_NOTLOGGEDIN, PUIS_LOGIN,
	PUIS_IDLE,
	PUIS_REQLIST, PUIS_RECVLIST, PUIS_ADDPKG, PUIS_UPDATEPKG, PUIS_REMOVEPKG
};

PatchUI::PatchUI(HWND hParent, HINSTANCE hInstance)
	: SyncDialog(hParent, hInstance), NamedPipe(1024)
{
	SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
	InitCommonControls();
	SetCurrentDirectory();

	cList = (ListView*)0;
	cState = PUIS_NOTCONNECTED;
	cServerErr = APE_NONE;
	cPendingIndex = -1;
	cPendingPkg = (PackageDescription*)0;

	chDrop = (HANDLE)0;
	cDropCurrent = cDropTotal = 0;
}

PatchUI::~PatchUI()
{
	if (chDrop)
	{
		DragFinish((HDROP)chDrop);
		chDrop = (HANDLE)0;
		cDropCurrent = cDropTotal = 0;
	}

	if (cPendingPkg)
	{
		delete cPendingPkg;
		cPendingPkg = (PackageDescription*)0;
	}
	delete cList;
	cList = (ListView*)0;
}

void
PatchUI::SetCurrentDirectory()
{
	TCHAR exePath[MAX_PATH + 1];
	int templen;

	templen = (int)GetModuleFileName(NULL, exePath, MAX_PATH);

	while (templen && exePath[templen - 1] != '\\')
		templen--;

	if (templen > 2)
		exePath[templen] = '\0';

	::SetCurrentDirectory((LPTSTR)exePath);
	UDEBUGMSG((exePath));
}
int
PatchUI::Run(void)
{
	hDialog = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_PKGMAINT), hParent,
		(DLGPROC)topDialogProc, (LPARAM)this);

	return SUPER::Run();
}

void
PatchUI::Init()
{
	CasinoBase::load();

	BrandTitle();

	cList = new ListView(GetDlgItem(hDialog, IDC_PKGLIST));

	PostMessage(hDialog, PUI_CONNECT, 0, 0);
}

void
PatchUI::BrandTitle()
{
	TCHAR wintitle[256], *probe;

	// Brand the title and show which casino it controls
	//
	probe = wintitle;
	_tcscpy(probe, _T("Casino App "));			
	probe += _tcslen(probe);
	_tcscpy(probe, CasinoBase::basename);
	probe += _tcslen(probe);
	*probe++ = ' ';
	SendMessage(hDialog, WM_GETTEXT,
		(WPARAM)(256 - (probe - wintitle)), (LPARAM)probe);
	SendMessage(hDialog, WM_SETTEXT, 0, (LPARAM)wintitle);
}

bool8
PatchUI::doUnzipPackage(ZipFile &ZF, PathName &Dest)
{
	ZipEntry* ze;
	pCStream out = (pCStream)0;
	UnzipStream* uzs = (UnzipStream*)0;
	int32 amt, origDestLen;
	uint8 buf[4096];

	// caller must clean up Dest directory upon failure
	//
	origDestLen = Dest.getLen();

	ze = ZF.getZipEntries();
	while (ze)
	{
		if (ze->isFilenameValid() && !ze->isEncrypted())
		{
			if (!Dest.concat(ze->filename))
				goto abort;

			// change unix separators (stored in zipfile)
			//
			Dest.replace('/', Directory::sep);

			if (ze->isDirectory())
			{
				if (!Directory::makeAll(Dest))
					goto abort;
			}
			else
			{
				if (!Directory::makeAll(Dest, Directory::EndIsFilename))
					goto abort;

				out = newCStream(Dest, streamO_WRONLY);
				if (!out)
					goto abort;

				uzs = ze->getUnzipStream(&ZF);
				if (uzs)
				{
					while ((amt = uzs->read(buf, 4096)) >= 0)
					{
						if (amt > 0 && amt != CStreamWrite(out, buf, amt))
						{
							amt = -2;
							break;
						}
					}

					if (amt < -1)
						goto abort;

					deleteCStream(out);
					delete uzs;
					out = (pCStream)0;
					uzs = (UnzipStream*)0;
				}
				else
					goto abort;
			}

			Dest.shrink(Dest.getLen() - origDestLen);
		}
		else
			goto abort;

		ze = ze->getNext();
	}

	return true;

abort:
	delete uzs;
	deleteCStream(out);
	Dest.shrink(Dest.getLen() - origDestLen);

	return false;
}

bool8 
PatchUI::unzipPackage(PackageDescription *pkg)
{
	bool8 result = true;

	ZipFile zf;
	PathName unzippeddir;

	unzippeddir.set(pkg->getZipName());
	unzippeddir.shrink(4);

	if(!zf.open(pkg->getZipName()))
		return false;

	if(!doUnzipPackage(zf, unzippeddir))
		result = false;

	zf.close();

	return result;
}

void
PatchUI::Add()
{
	PackageDescription* pkg = new PackageDescription;

	if (pkg)
	{
		UpdateDlg update(hInst, hDialog, UpdateDlg::ADD, pkg);

		if (IDOK == update.Run() && pkg->getName() && pkg->getZipName())
		{
			if (!doAddPkg(pkg))
				delete pkg;
		}
		else
			delete pkg;
	}
	else
		MessageBox(hDialog, _T("Failed to create local pkg description."),
			_T("Memory Error"), MB_ICONINFORMATION | MB_OK);
}

void
PatchUI::Remove()
{
	PackageDescription* pkg;
	int32 victim;
	TCHAR msg[256];

	victim = cList->getSelectedItem();
	if (victim == -1)
		return;

	pkg = cPkgs.getItem(victim);
	if (!pkg)
		return;

	if (pkg->getName() && 0 == _tcsicmp(pkg->getName(), _T("Lobby")))
		MessageBox(hDialog, _T("You may only Update the Lobby."),
			_T("Error"), MB_ICONERROR | MB_OK);
	else if (pkg->getName() && 0 == _tcsicmp(pkg->getName(), _T("Bank")))
		MessageBox(hDialog, _T("You may only Update the Bank."),
			_T("Error"), MB_ICONERROR | MB_OK);
	else
	{
		wsprintf(msg, _T("Remove \"%s\""), pkg->getName() ? pkg->getName() : _T(""));

		if (IDYES ==
			MessageBox(hDialog, _T("Are you sure?\r\n\r\nSelecting \"Yes\" will ")
				_T("uninstall this package on each\r\nclient machine ")
				_T("(when attempting to play)."), msg, MB_ICONWARNING | MB_YESNO))
		{
			netRemovePkg(victim);

			PathName unzippeddir;
			unzippeddir.set(pkg->getZipName());
			unzippeddir.shrink(4);
			Directory::removeAll(unzippeddir);
		}
	}
}

void
PatchUI::Update()
{
	PackageDescription* pkg;
	int32 index;

	index = cList->getSelectedItem();
	if (index == -1)
		return;

	pkg = cPkgs.getItem(index);
	if (pkg)
	{
		PackageDescription* pkgCopy = new PackageDescription(*pkg);

		if (pkgCopy)
		{
			UpdateDlg update(hInst, hDialog, UpdateDlg::UPDATE, pkgCopy);

			if (IDOK == update.Run() &&
				pkgCopy->getName() && pkgCopy->getZipName())
			{
				if (!doUpdatePkg(index, pkg, pkgCopy))
					delete pkgCopy;
			}
			else
				delete pkgCopy;
		}
		else
			MessageBox(hDialog, _T("Failed to create local pkg description."),
				_T("Memory Error"), MB_ICONINFORMATION | MB_OK);
	}
}

bool8
PatchUI::doAddPkg(PackageDescription* pkg)
{
	const String& name = pkg->getName();

	std::vector<char> getName( name.getLength() + 1 );

	Library::StringConversions::convert( &getName[0], name, name.getLength() );

	const String& zipname = pkg->getZipName();

	std::vector<char> getZipName( zipname.getLength() + 1 );
	
	Library::StringConversions::convert( &getZipName[0], zipname, zipname.getLength() );

	if (!cPkgs.contains(&getName[0], &getZipName[0]))
	{
		if (unzipPackage(pkg)) 
		{
			netAddPkg(pkg);
			return true;
		}
		else
		{
			TCHAR msg[1024];

			wsprintf(msg, L"Unable to unzip package.\r\n[%s (%s)]",
				pkg->getName(),
				pkg->getZipName());

			MessageBox(hDialog, msg, _T("Error"), MB_ICONERROR | MB_OK);
		}
	}
	else
	{
		TCHAR msg[1024];

		wsprintf(msg, L"Package already exists.\r\n[%s (%s)]",
			pkg->getName(),
			pkg->getZipName());

		MessageBox(hDialog, msg, _T("Error"), MB_ICONERROR | MB_OK);
	}
	return false;
}

bool8
PatchUI::doUpdatePkg(int32 curIndex, PackageDescription* current,
	PackageDescription* update)
{
	bool8 validUpdate = (bool8)
		(0 == update->getName().caseCompare(current->getName()) &&
		0 != update->getZipName().caseCompare(current->getZipName()) &&
		update->getVersion() != current->getVersion());

	if (validUpdate)
	{
		// remove the old unzipped package first
		PathName unzippeddir;
		unzippeddir.set(current->getZipName());
		unzippeddir.shrink(4);
		Directory::removeAll(unzippeddir);

		if (unzipPackage(update)) 
		{
			netUpdatePkg(curIndex, update);
			return true;
		}
		else
		{
			TCHAR msg[1024];

			wsprintf(msg, L"Unable to unzip package.\r\n[%s (%s)]",
				current->getName(),
				current->getZipName());

			MessageBox(hDialog, msg, _T("Error"), MB_ICONERROR | MB_OK);
		}
	}
	else
	{
		TCHAR msg[1024];

		wsprintf(msg, L"Package already exists.\r\n[%s (%s)]",
			current->getName(),
			current->getZipName());

		MessageBox(hDialog, msg, _T("Error"), MB_ICONERROR | MB_OK);
	}
	return false;
}

int32
PatchUI::addLV(PackageDescription* pkg)
{
	TCHAR text[256];
	TCHAR depends[1024];

	wsprintf(text, L"%d.%d",
		HIWORD(pkg->getVersion()), LOWORD(pkg->getVersion()));

	depends[0] = '\0';
	for(int32 i=0;i<pkg->getNumDependencies();i++)
	{
		TCHAR buffer[256];

		wsprintf(buffer, L"%s=%d.%d", pkg->getDepencies()[i].getName(),
			HIWORD(pkg->getDepencies()[i].getRequiredVersion()), LOWORD(pkg->getDepencies()[i].getRequiredVersion()));
		
		_tcscat(depends, buffer);

		if (i+1 != pkg->getNumDependencies())
			_tcscat(depends, _T(", "));
	}


	return cList->addItem(&ConvertStringToTCharVector(pkg->getName())[0],
						  &ConvertStringToTCharVector(text)[0], 
						  &ConvertStringToTCharVector(depends)[0],
						  &ConvertStringToTCharVector(pkg->getZipName())[0]);
}

void
PatchUI::updateLV(int32 Index, PackageDescription* pkg)
{
	TCHAR text[256];

	wsprintf(text, L"%d.%d",
		(pkg->getVersion() >> 16) & 0xFFFFL,
		pkg->getVersion() & 0xFFFFL);

	cList->changeItem(Index, &ConvertStringToTCharVector(pkg->getName())[0], 
							 &ConvertStringToTCharVector(text)[0],
							 &ConvertStringToTCharVector(pkg->getZipName())[0]);
}

BOOL
PatchUI::DrawItem(const DRAWITEMSTRUCT * lpDrawItem)
{
    if (lpDrawItem->CtlType != ODT_LISTVIEW)
        return FALSE;

    switch (lpDrawItem->itemAction)
    {
        case ODA_DRAWENTIRE:
        case ODA_FOCUS:
        case ODA_SELECT:
			cList->DrawItem((LPDRAWITEMSTRUCT)lpDrawItem);
            break;
    }

    return TRUE;
}

void
PatchUI::MeasureItem(MEASUREITEMSTRUCT * lpMeasureItem)
{
    if (lpMeasureItem->CtlType != ODT_LISTVIEW)
        return;

	cList->MeasureItem(lpMeasureItem);
}

int32
PatchUI::Notify(int idCtl, LPNMHDR pnmh)
{
    switch (pnmh->code)
    {
		case NM_DBLCLK:
			if (idCtl == IDC_PKGLIST)
				Update();
			break;

        case HDN_DIVIDERDBLCLICK:
        case HDN_ENDTRACK:
        {
            HD_NOTIFY *phdn = (HD_NOTIFY *)pnmh;

            PostMessage(hDialog, WM_UPDATEHEADERWIDTH,
				(WPARAM)phdn->hdr.hwndFrom, (LPARAM)phdn->iItem);
            break;
        }
    }
    return 0;
}

void
PatchUI::dropFiles(WPARAM wParam)
{
	if (!chDrop && cState == PUIS_IDLE)
	{
		chDrop = (HANDLE)wParam;
		cDropTotal = DragQueryFile((HDROP)chDrop, (UINT)-1, NULL, 0);
		cDropCurrent = 0;

		continueDrop();
	}
}

void
PatchUI::continueDrop()
{
	if (chDrop)
	{
		if (cDropCurrent < cDropTotal)
		{
			uint32 pathsz;
			TCHAR* path;
			DWORD fileAttr;

			pathsz = DragQueryFile((HDROP)chDrop, cDropCurrent, NULL, 0) + 1;
			path = new TCHAR[pathsz];
			if (path)
			{
				DragQueryFile((HDROP)chDrop, cDropCurrent, path, pathsz);

				fileAttr = GetFileAttributes(path);

				if (fileAttr != 0xFFFFFFFFL)
				{
					if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY) == 0)
						addDropFile(&ConvertStringToTCharVector(path)[0]);
				}

				delete [] path;
			}
			++cDropCurrent;

			if (cState == PUIS_IDLE)	// there was a problem
				continueDrop();
		}
		else
		{
			DragFinish((HDROP)chDrop);

			chDrop = (HANDLE)0;
			cDropCurrent = cDropTotal = 0;
		}
	}
}

void
PatchUI::addDropFile(TCHAR* path)
{
	PathName src;

	src.set(path);
	const TCHAR* ext = _tcschr(src.getEnd(), '.');

	if (ext && 0 == _tcsicmp(ext, _T(".zip")))
	{
		PackageDescription* pkg = new PackageDescription;

		if (pkg)
		{
			if (pkg->setZipFile(path))
			{
				PackageDescription* already;
				int32 alreadyIndex = -1;

				already = cPkgs.getItem(&ConvertStringToCharVector(pkg->getName())[0], 
										&ConvertStringToCharVector(pkg->getZipName())[0],
										&alreadyIndex);

				if (!already)
				{
					if (!doAddPkg(pkg))
						delete pkg;
				}
				else
				{
					if (!doUpdatePkg(alreadyIndex, already, pkg))
						delete pkg;
				}
			}
			else
			{
				TCHAR msg[1024];
				wsprintf(msg, L"Invalid Casino Client Package.\r\n[%s].", path);
				MessageBox(hDialog, msg, _T("Error"), MB_ICONERROR | MB_OK);
				delete pkg;
			}
		}
		else
		{
			TCHAR msg[1024];
			wsprintf(msg,
				L"Failed to create local pkg description for\r\n[%s].", path);
			MessageBox(hDialog, msg, _T("Memory Error"), MB_ICONERROR | MB_OK);
		}
	}
}

BOOL
PatchUI::dialogProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	switch (Msg)
	{
		case WM_INITDIALOG:
			Init();
			return TRUE;

		case WM_DROPFILES:
			dropFiles(wParam);
			return 0;

		case WM_COMMAND:
			switch (LOWORD(wParam))
			{
				case IDCANCEL:
					break;

				case IDOK:
					EndDialog(hWnd, APE_NONE);
					return TRUE;

				case IDC_ADD:
					Add();
					break;

				case IDC_REMOVE:
					Remove();
					break;

				case IDC_UPDATE:
					Update();
					break;
			}
			break;

		case WM_DRAWITEM:
			return DrawItem((const DRAWITEMSTRUCT*)lParam);
		case WM_MEASUREITEM:
			MeasureItem((MEASUREITEMSTRUCT*)lParam);
			break;
		case WM_NOTIFY:
			return Notify((int)wParam, (NMHDR*)lParam);
		case WM_UPDATEHEADERWIDTH:
			cList->updateHeaderWidth((HWND)wParam, (INT)lParam);
			return TRUE;

		case PUI_CONNECT:
			netConnect();
			return TRUE;
		case PUI_LOGIN:
			netLogin();
			return TRUE;
	}

	return SUPER::dialogProc(hWnd, Msg, wParam, lParam);
}

void
PatchUI::netConnect()
{
	TCHAR pipename[256];

	wsprintf(pipename, PATCHPIPE, (const uint16*)CasinoBase::basename);
	
	if (!open(pipename))
		fail(_T("Failed to connect to server."));
	else
	{
		cState = PUIS_NOTLOGGEDIN;
		PostMessage(hDialog, PUI_LOGIN, 0, 0);
	}
}

void
PatchUI::netLogin()
{
	uint8 msg[2];

	// todo: secure this login (both sides)
	//
	if (cState != PUIS_NOTLOGGEDIN)
	{
		fail(_T("Login failed."));
		return;
	}

	cState = PUIS_LOGIN;
	msg[0] = 0x02;
	msg[1] = 'P';

	if (!send(msg, 2))
		fail(_T("Communication error."));
}

void
PatchUI::netReqList(bool8 ReqFromBeginning)
{
	uint8 msg[2];

	if (ReqFromBeginning && cState != PUIS_IDLE ||
		!ReqFromBeginning && cState != PUIS_RECVLIST)
	{
		fail(_T("Req packages failed."));
		return;
	}

	cState = ReqFromBeginning ? PUIS_REQLIST : PUIS_RECVLIST;
	msg[0] = 0x02;
	msg[1] = (uint8)(ReqFromBeginning ? 'L' : 'l');

	if (!send(msg, 2))
		fail(_T("Communication error."));
}

void
PatchUI::netAddPkg(PackageDescription* newPkg)
{
	uint8 msg[256];
	int32 msglen, len;

	if (cState != PUIS_IDLE)
	{
		delete newPkg;
		MessageBox(hDialog, _T("Add package failed (bad state)."), _T("Error"), MB_OK);
		return;
	}

	if (!newPkg || !newPkg->getName() || !newPkg->getZipName() ||
		2 + newPkg->getName().getLength() + 4 + newPkg->getZipName().getLength() > 255)
	{
		delete newPkg;
		MessageBox(hDialog, _T("Add package failed (invalid pkg)."), _T("Error"),
			MB_OK);
		return;
	}

	cPendingPkg = newPkg;
	cState = PUIS_ADDPKG;

	msglen = 2;		// skip header bytes <msglen><A>

	len = newPkg->getName().getLength();
	msg[msglen++] = (uint8)(len & 0xff);
	memcpy(msg + msglen, newPkg->getName(), len);
	msglen += len;

	msg[msglen++] = (uint8)((newPkg->getVersion() >> 24) & 0xff);
	msg[msglen++] = (uint8)((newPkg->getVersion() >> 16) & 0xff);
	msg[msglen++] = (uint8)((newPkg->getVersion() >> 8) & 0xff);
	msg[msglen++] = (uint8)(newPkg->getVersion() & 0xff);
	
	len = newPkg->getZipName().getLength();
	msg[msglen++] = (uint8)(len & 0xff);
	memcpy(msg + msglen, newPkg->getZipName(), len);
	msglen += len;

	msg[0] = (uint8)(msglen & 0xff);
	msg[1] = 'A';

	if (!send(msg, msglen))
		fail(_T("Communication error."));
}

void
PatchUI::netRemovePkg(int32 Victim)
{
	uint8 msg[3];

	if (cState != PUIS_IDLE)
	{
		MessageBox(hDialog, _T("Remove package failed (bad state)."), _T("Error"),
			MB_OK);
		return;
	}

	cPendingIndex = Victim;
	cState = PUIS_REMOVEPKG;

	msg[0] = 0x03;
	msg[1] = 'R';
	msg[2] = (uint8)(Victim & 0xff);

	if (!send(msg, 3))
		fail(_T("Communication error."));
}

void
PatchUI::netUpdatePkg(int32 Index, PackageDescription* newUpdate)
{
	uint8 msg[256];
	int32 msglen, len;

	if (cState != PUIS_IDLE)
	{
		delete newUpdate;
		MessageBox(hDialog, _T("Update package failed (bad state)."), _T("Error"),
			MB_OK);
		return;
	}

	if (Index < 0 || Index >= cPkgs.getCount() ||
		!newUpdate || !newUpdate->getName() || !newUpdate->getZipName() ||
		3 + newUpdate->getName().getLength() + 4 +
			newUpdate->getZipName().getLength() > 255)
	{
		delete newUpdate;
		MessageBox(hDialog, _T("Update package failed (invalid pkg)."), _T("Error"),
			MB_OK);
		return;
	}

	cPendingIndex = Index;
	cPendingPkg = newUpdate;
	cState = PUIS_UPDATEPKG;

	msglen = 2;		// skip header bytes <msglen><U>

	msg[msglen++] = (uint8)(Index & 0xff);

	len = newUpdate->getName().getLength();
	msg[msglen++] = (uint8)(len & 0xff);
	memcpy(msg + msglen, newUpdate->getName(), len);
	msglen += len;

	msg[msglen++] = (uint8)((newUpdate->getVersion() >> 24) & 0xff);
	msg[msglen++] = (uint8)((newUpdate->getVersion() >> 16) & 0xff);
	msg[msglen++] = (uint8)((newUpdate->getVersion() >> 8) & 0xff);
	msg[msglen++] = (uint8)(newUpdate->getVersion() & 0xff);
	
	len = newUpdate->getZipName().getLength();
	msg[msglen++] = (uint8)(len & 0xff);
	memcpy(msg + msglen, newUpdate->getZipName(), len);
	msglen += len;

	msg[0] = (uint8)(msglen & 0xff);
	msg[1] = 'U';

	if (!send(msg, msglen))
		fail(_T("Communication error."));
}

void
PatchUI::serverError(TCHAR* Msg, int32 Len)
{
	TCHAR *text;
	bool8 isFatal = true;

	if (Len < 3)
		text = _T("Unrecognized server error.");
	else
	{
		switch (Msg[2])
		{
			case APE_NONE:
				text = (TCHAR*)0;
				break;
			case APE_UNKNOWN_REQUEST:
				text = _T("Unrecognized request.");
				isFatal = false;
				break;
			case APE_MEMORY_ERROR:
				text = _T("Server memory error.");
				isFatal = false;
				break;
			case APE_ATTEMPT_RELOGIN:
				text = _T("Attempted re-login.");
				break;
			case APE_LIST_NO_MORE:
				text = (TCHAR*)0;
				break;
			case APE_UNKNOWN_PACKAGE:
				text = _T("Unknown package.");
				isFatal = false;
				break;
			case APE_INVALID_UPDATE:
				text = _T("Invalid update.");
				isFatal = false;
				break;
			case APE_INVALID_ADD:
				text = _T("Invalid add.");
				isFatal = false;
				break;
			case APE_INVALID_REMOVE:
				text = _T("Can't remove the Lobby package (only update).");
				isFatal = false;
				break;
			default:
				text = _T("Unrecognized server error");
				break;
		}
	}

	if (text)
	{
		cServerErr = (int32)Msg[2];
		fail(text, isFatal);
	}
}

void
PatchUI::fail(TCHAR* Msg, bool8 Fatal)
{
	MessageBox(hDialog, Msg, _T("Error"), MB_ICONERROR | MB_OK);

	if (Fatal)
	{
		if (cServerErr == APE_NONE)
			cServerErr = APE_UI_INTERNAL;

		EndDialog(hDialog, cServerErr);
	}
}

void
PatchUI::ioerror()
{
	fail(_T("Communication error."));
}

void
PatchUI::recvd(uint8* Msg, int32 Len)
{
	if (Len == 0 || Len < Msg[0])
	{
		UDEBUGMSG((L"partial msg received"));
		return;
	}

	switch (cState)
	{
		case PUIS_LOGIN:
			loginRecvd(Msg, Len);
			break;
		case PUIS_REQLIST:
		case PUIS_RECVLIST:
			listRecvd(Msg, Len);
			break;
		case PUIS_ADDPKG:
			addPkgRecvd(Msg, Len);
			break;
		case PUIS_UPDATEPKG:
			updatePkgRecvd(Msg, Len);
			break;
		case PUIS_REMOVEPKG:
			removePkgRecvd(Msg, Len);
			break;

		case PUIS_IDLE:
			UDEBUGMSG((L"Unknown IDLE data ignored (0x%02%02x)",
				Len & 0xff, Msg[0]));
			break;
		default:
		case PUIS_NOTCONNECTED:
			fail(_T("State error (recvd)."));
			break;
	}
}

void
PatchUI::loginRecvd(uint8* Msg, int32 Len)
{
	if (Msg[1] == 'S')			// success
	{
		cState = PUIS_IDLE;
		netReqList(true);
	}
	else if (Msg[1] == 'E')		// error
		serverError((TCHAR*)Msg, Len);
	else
		fail(_T("Communication error."));
}

void
PatchUI::listRecvd(uint8* Msg, int32 Len)
{
	int32 used = 0;

	switch (cState)
	{
		case PUIS_REQLIST:
			if (Msg[1] == 'S')			// success
			{
				used = (int32)Msg[0];

				if (Msg[2] < 0x01)		// protocol version
				{
					fail(_T("_T(Patch server speaks nonsense."));
					return;
				}

				cCountToRecv = (int32)Msg[3]; 
				cWorkingIndex = 0;
				cState = PUIS_RECVLIST;

				Len -= used;
			}
			else if (Msg[1] == 'E')		// error
			{
				cState = PUIS_IDLE;
				serverError((TCHAR*)Msg, Len);
				break;
			}
			else
			{
				fail(_T("Communication error."));
				break;
			}
		//
		// fall-through to receive data
		//
		case PUIS_RECVLIST:
		{
			int32 i;
			uint8* m = Msg + used;
			PackageDescription *pkg = (PackageDescription*)0;
			uint8 buf[1024];

			while (cWorkingIndex < cCountToRecv && Len > 0 && Len >= m[0])
			{
				if (m[1] == 'L')			// data
				{
					pkg = new PackageDescription();
					if (!pkg)
					{
						fail(_T("Out of memory"));
						break;
					}

					i = (int32)m[2];
					if (i > 0 && i < 1024)
					{
						memcpy(buf, m + 3, i);
						buf[i] = '\0';
						i += 3;
					}
					else
					{
						buf[0] = '\0';
						i = 3;
					}
					if (!pkg->setZipFile((const char*) buf))
						pkg->setUnknown();

					cPkgs.addItem(pkg);
					addLV(pkg);
					pkg = (PackageDescription*)0;

					used += (int32)m[0];
					Len -= (int32)m[0];
					m += (int32)m[0];
					++cWorkingIndex;
				}
				else if (m[1] == 'E')
				{
					cState = PUIS_IDLE;
					serverError((TCHAR*)m, Len);
					break;
				}
				else
				{
					fail(_T("Communication error."));
					break;
				}
			}

			if (pkg)
				delete pkg;

			if (cServerErr == APE_NONE)
			{
				if (cWorkingIndex == cCountToRecv)
					cState = PUIS_IDLE;
				else
					netReqList(false);
			}
			break;
		}

		default:
			fail(_T("Communication error."));
			break;
	}
}

void
PatchUI::addPkgRecvd(uint8* Msg, int32 Len)
{
	cState = PUIS_IDLE;

	if (Msg[1] == 'S')			// success
	{
		if (cPendingPkg)
		{
			int32 index;

			cPkgs.addItem(cPendingPkg);
			index = addLV(cPendingPkg);
			cList->selectItem(index);
		}
		else
			fail(_T("Package add on server but not locally (fatal).")); 

		cPendingPkg = (PackageDescription*)0;
	}
	else if (Msg[1] == 'E')		// error
	{
		delete cPendingPkg;
		cPendingPkg = (PackageDescription*)0;
		serverError((TCHAR*)Msg, Len);
	}
	else
	{
		delete cPendingPkg;
		cPendingPkg = (PackageDescription*)0;
		fail(_T("Communication error."));
	}
	continueDrop();
}

void
PatchUI::removePkgRecvd(uint8* Msg, int32 Len)
{
	cState = PUIS_IDLE;

	if (Msg[1] == 'S')			// success
	{
		PackageDescription* pkg;

		pkg = cPkgs.getItem(cPendingIndex);

		if (pkg)
		{
			cPkgs.removeItem(pkg);
			delete pkg;

			cList->deleteItem(cPendingIndex);
			if (cPendingIndex >= cPkgs.getCount())
				--cPendingIndex;
			cList->selectItem(cPendingIndex);
		}
		else
			fail(_T("Package removed on server but not locally (fatal).")); 

		cPendingIndex = -1;
	}
	else if (Msg[1] == 'E')		// error
	{
		cPendingIndex = -1;
		serverError((TCHAR*)Msg, Len);
	}
	else
	{
		cPendingIndex = -1;
		fail(_T("Communication error."));
	}
}

void
PatchUI::updatePkgRecvd(uint8* Msg, int32 Len)
{
	cState = PUIS_IDLE;

	if (Msg[1] == 'S')			// success
	{
		PackageDescription* dest = cPkgs.getItem(cPendingIndex);

		if (cPendingPkg && dest)
		{
			dest->copy(cPendingPkg);
			updateLV(cPendingIndex, dest);
			cList->selectItem(cPendingIndex);

			delete cPendingPkg;
			cPendingPkg = (PackageDescription*)0;
		}
		else
		{
			delete cPendingPkg;
			cPendingPkg = (PackageDescription*)0;

			fail(_T("Package add on server but not locally (fatal).")); 
		}
		cPendingIndex = -1;
	}
	else if (Msg[1] == 'E')		// error
	{
		cPendingIndex = -1;
		delete cPendingPkg;
		cPendingPkg = (PackageDescription*)0;

		serverError((TCHAR*)Msg, Len);
	}
	else
	{
		cPendingIndex = -1;
		delete cPendingPkg;
		cPendingPkg = (PackageDescription*)0;

		fail(_T("Communication error."));
	}

	continueDrop();
}
